package top.dotgo.dao.base;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.querydsl.core.QueryResults;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.*;
import com.querydsl.core.types.dsl.EntityPathBase;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.core.types.dsl.StringPath;
import com.querydsl.jpa.impl.JPAUpdateClause;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import top.dotgo.kit.bean.Page;
import top.dotgo.model.BaseQuery;
import top.dotgo.model.IBaseEntity;

import javax.persistence.Id;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static cn.hutool.core.util.ReflectUtil.getFieldValue;

/**
 * @author : lin
 * @date : 2019/10/11 18:48
 */
@Slf4j
@SuppressWarnings("AlibabaClassNamingShouldBeCamel")
public class Q {

    public static final String PER = "%";
    public static final String ENTITY = "entity";
    public static final int OPS_INDEX = 2;

    private Q() {
    }

    /**
     * 根据实体类 获取对应的 EntityPath
     *
     * @param obj obj
     * @param <T> T
     * @return EntityPath
     */
    public static <T extends IBaseEntity> EntityPath<T> entityPath(T obj) {
        Class<T> clazz = ClassUtil.getClass(obj);
        return entityPath(clazz);
    }

    /**
     * 根据Class<T> 获取对应的 EntityPath
     *
     * @param <T> T
     * @return EntityPath
     */
    public static <T extends IBaseEntity> EntityPath<T> entityPath(Class<T> clazz) {
        String entityName = clazz.getSimpleName();
        return entityPath(clazz, entityName);
    }

    /**
     * 获取 EntityPath<T> 获取对应的 EntityPath
     *
     * @param clazz 类型
     * @param name  列名 或 表名
     * @param <T>   T
     * @return EntityPath<T>
     */
    public static <T> EntityPath<T> entityPath(Class<T> clazz, String name) {
        return entityPathBase(clazz, StrUtil.lowerFirst(name));
    }

    /**
     * 获取EntityPath
     *
     * @param clazz  clazz
     * @param column column
     * @param <T>    T
     * @return EntityPath<T>
     */
    public static <T> EntityPath<T> entityPathBase(Class<T> clazz, String column) {
        return new EntityPathBase<>(clazz, column);
    }

    /**
     * 根据 class 获取 Expression
     *
     * @param clazz clazz
     * @param <T>   <T>
     * @return Expression<T>
     */
    public static <T extends IBaseEntity> Expression<T> expression(Class<T> clazz) {
        return entityPath(clazz);
    }

    /**
     * 将实体类中的属性 赋值给 update set中
     *
     * @param clause clause
     * @param obj    obj
     * @param <T>    T
     */
    public static <T extends IBaseEntity> void setPathValue(JPAUpdateClause clause, T obj) {
        Class<T> clazz = ClassUtil.getClass(obj);
        for (Field field : ClassUtil.getDeclaredFields(clazz)) {
            Object value = getFieldValue(obj, field);
            if (value != null) {
                PathBuilder<Object> builder = new PathBuilder<>(clazz, field.getName());
                clause.set(builder, value);
            }
        }
    }

    /**
     * 根据 obj 的值 生成 查询条件 默认 equals
     * <p>
     *
     * @param obj IBaseEntity
     * @param <T> <T> IBaseEntity
     * @return Predicate[]
     */
    public static <T extends IBaseEntity> Predicate[] getPredicates(T obj) {
        return getPredicates(obj, (Predicate) null);
    }

    /**
     * 根据 obj 的值 生成 查询条件 默认 equals
     *
     * @param obj       IBaseEntity
     * @param predicate predicate
     * @param <T>       <T>
     * @return Predicate[]
     */
    public static <T extends IBaseEntity> Predicate[] getPredicates(T obj, Predicate... predicate) {
        List<Predicate> predicateList = new ArrayList<>();

        //处理关联表查询
        for (Field field : ClassUtil.getDeclaredFields(obj.getClass())) {
            //如果字段不是 BaseQueryBean类型，代表查询当前实体
            if (!BaseQuery.class.isAssignableFrom(field.getType())) {
                addPredicate(obj, field, predicateList);
                continue;
            }
            //字段是 BaseQueryBean类型的
            Class<?> clazz = field.getType();

            Object joinBean = getFieldValue(obj, field.getName());
            //TODO 目前支持关联查询层级为 1级
            for (Field joinField : ClassUtil.getDeclaredFields(clazz)) {
                if (!BaseQuery.class.isAssignableFrom(joinField.getType())) {
                    addPredicate(joinBean, joinField, predicateList);
                }
            }
        }

        if (predicate != null && predicate.length > 0 && predicate[0] != null) {
            predicateList.addAll(Arrays.asList(predicate));
        }
        return predicateList.isEmpty() ? null : predicateList.toArray(new Predicate[0]);
    }

    /**
     * 拼接 Predicate
     *
     * @param obj           obj
     * @param field         field
     * @param predicateList predicateList
     * @param <T>           T
     */
    private static <T> void addPredicate(T obj, Field field, List<Predicate> predicateList) {
        if (obj == null || field == null) {
            return;
        }

        //过滤掉 entity
        if (field.getName().equals(ENTITY)) {
            return;
        }


        //真实类型 默认 当前对象类型
        Class<?> clazz = ClassUtil.getClass(obj);

        //如果当前类型是 BaseQueryBean 的子类
        if (obj instanceof BaseQuery) {
            //如果包含entity 字段 则 真实类型 取 entity字段的类型 如 QueryBean
            Field entityField = ClassUtil.getDeclaredField(clazz, ENTITY);
            if (entityField != null) {
                clazz = entityField.getType();
            }
        }
        field = ClassUtil.getDeclaredField(clazz, field.getName());

        if (field == null) {
            return;
        }

        //获取字段值
        Object value = getFieldValue(obj, field.getName());
        if (value == null) {
            return;
        }

        //获取自定义操作符
        ops opt = getQops(value);

        //获取条件
        List<Expression<?>> temp = getConstant(getFieldType(field), value);

        if (!temp.isEmpty()) {
            String className = StrUtil.lowerFirst(clazz.getSimpleName());
            String columnName = StrUtil.format("{}.{}", className, field.getName());
            PathBuilder<Object> builder = new PathBuilder<>(field.getType(), columnName);
            List<Expression<?>> list = new ArrayList<>();
            list.add(builder);
            list.addAll(temp);
            //组装成 Predicate
            PredicateOperation operation = ExpressionUtils.predicate(getOps(value), list.toArray(new Expression<?>[0]));
            //not like  not between
            if (ops.nl.equals(opt) || ops.nb.equals(opt)) {
                operation = ExpressionUtils.predicate(Ops.NOT, operation);
            }
            predicateList.add(operation);
        }
    }

    /**
     * 根据自定义对象 生成 OrderSpecifier
     *
     * @param obj 自定义对象  需包含 asc、desc 字段
     * @param <T> T
     * @return OrderSpecifier<?>[]
     */
    @SuppressWarnings("unchecked")
    @SneakyThrows
    public static <T extends IBaseEntity> OrderSpecifier<String>[] getOrder(T obj) {
        List<OrderSpecifier<String>> orderList = new ArrayList<>();
        Field ascField = ReflectUtil.getField(obj.getClass(), "asc");
        Field descField = ReflectUtil.getField(obj.getClass(), "desc");
        if (ascField != null) {
            Object value = getFieldValue(obj, "asc");
            if (value != null) {
                String asc = value.toString();
                for (String s : asc.split(StrUtil.COMMA)) {
                    Field field = ReflectUtil.getField(obj.getClass(), s);
                    if (field != null) {
                        PathBuilder<String> builder = new PathBuilder<>(String.class, field.getName());
                        orderList.add(new OrderSpecifier<>(Order.ASC, builder));
                    }
                }
            }
        }
        if (descField != null) {
            Object value = getFieldValue(obj, "desc");
            if (value != null) {
                String desc = value.toString();
                for (String s : desc.split(StrUtil.COMMA)) {
                    Field field = ReflectUtil.getField(obj.getClass(), s);
                    if (field != null) {
                        PathBuilder<String> builder = new PathBuilder<>(String.class, field.getName());
                        orderList.add(new OrderSpecifier<>(Order.DESC, builder));
                    }
                }
            }
        }
        return orderList.toArray(new OrderSpecifier[0]);
    }

    /**
     * 根据 page size 计算 offset
     *
     * @param page page
     * @param size size
     * @return offset
     */
    public static int offset(Integer page, Integer size) {
        if (page == null) {
            page = 1;
        } else {
            page = page <= 0 ? 1 : page;
        }

        if (size == null) {
            size = 10;
        } else {
            size = size <= 0 ? 1 : size;
        }

        return (page - 1) * size;
    }

    /**
     * 计算分页 页码 page
     *
     * @param offset offset
     * @param size   size
     * @return page
     */
    public static int page(long offset, long size) {
        offset = offset <= 0 ? 1 : offset;
        size = (size <= 0 ? 1 : size);
        return (int) Math.ceil(NumberUtil.div(offset, size));
    }

    /**
     * QueryResults 转 page
     *
     * @param results results
     * @param <T>     <T>
     * @return Page<T>
     */
    public static <T extends IBaseEntity> Page<T> getPage(QueryResults<T> results) {
        //results.getOffset()整数 计算page时 会少一页
        return new Page<T>()
                .setPage(page(results.getOffset(), results.getLimit()) + (results.getOffset() > 0 ? 1 : 0))
                .setSize((int) results.getLimit())
                .setTotal(page(results.getTotal(), results.getLimit()))
                .setCount(Math.toIntExact(results.getTotal()))
                .setEmpty(results.isEmpty())
                .setList(results.getResults());
    }

    public static <T> Page<T> getPage(QueryResults<Tuple> results, Function<Tuple, T> convert) {
        //results.getOffset()整数 计算page时 会少一页 所以
        return new Page<T>()
                .setPage(page(results.getOffset(), results.getLimit()) + (results.getOffset() > 0 ? 1 : 0))
                .setSize((int) results.getLimit())
                .setTotal(page(results.getTotal(), results.getLimit()))
                .setCount(Math.toIntExact(results.getTotal()))
                .setEmpty(results.isEmpty())
                .setList(results.getResults().stream().map(convert).collect(Collectors.toList()));
    }

    /**
     * 根据 class 创建实体对象
     *
     * @param clazz clazz
     * @param <T>   T
     * @return T
     */
    public static <T extends IBaseEntity> T getBean(Class<T> clazz) {
        try {
            Constructor<T> constructor = clazz.getConstructor();
            if (constructor != null) {
                return constructor.newInstance();
            }
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            log.error("Q.getBean(clazz) is err e={}", e.getMessage());
        }
        return null;
    }

    /**
     * 获得主键 字段
     *
     * @param obj obj
     * @param <T> <T>
     * @param <P> <p>
     * @return P
     */
    @SuppressWarnings("unchecked")
    public static <T extends IBaseEntity, P> P getPkValue(T obj) {
        Field field = getPkField(obj.getClass());
        if (field == null) {
            return null;
        }
        return (P) getFieldValue(obj, field);
    }

    /**
     * 根据 class 获取实体类的 主键 字段名
     *
     * @param clazz clazz
     * @param <T>   T
     * @return P
     */
    public static <T extends IBaseEntity> String getPkName(Class<T> clazz) {
        Field field = getPkField(clazz);
        if (field == null) {
            return null;
        }
        return field.getName();
    }

    /**
     * 根据 clazz 获取 主键 字段
     *
     * @param clazz clazz
     * @param <T>   T
     * @return Field
     */
    private static <T extends IBaseEntity> Field getPkField(Class<T> clazz) {
        for (Field declaredField : ClassUtil.getDeclaredFields(clazz)) {
            Id annotation = AnnotationUtil.getAnnotation(declaredField, Id.class);
            if (annotation != null) {
                return declaredField;
            }
        }
        return null;
    }

    /**
     * 获取 字段的泛型类 class
     *
     * @param field field
     * @param <T>   T
     * @return Class<T>
     */
    @SuppressWarnings("unchecked")
    public static <T> Class<T> getFieldType(Field field) {
        Type type = field.getGenericType();
        if (type != null) {
            if (type instanceof ParameterizedType) {
                return (Class<T>) ((ParameterizedType) type).getActualTypeArguments()[0];
            }
            if (type.getTypeName().equals(StringPath.class.getName())) {
                return (Class<T>) String.class;
            }
        }
        return (Class<T>) field.getType();
    }

    /**
     * 根据对象字段值 解析 最终的参数值
     *
     * @param type  type
     * @param value 例如： "eq:张三"
     * @return Expression<?>
     */
    private static <T> List<Expression<?>> getConstant(Class<T> type, Object value) {
        List<Expression<?>> list = new ArrayList<>();
        ops ops = getQops(value);
        //如果是 eq 直接解析 成 Constant
        if (ops.equals(Q.ops.eq)) {
            return Collections.singletonList(ConstantImpl.create(Convert.convert(type, value)));
        }
        //获取 前端传过来的值 解析称真实值
        String val = StrUtil.subSuf(value.toString(), OPS_INDEX + 1);

        switch (ops) {
            case lk:
                //如果 like 不包含 % 默认两边都加上
                if (!val.contains(PER)) {
                    val = StrUtil.format("%{}%", val);
                }
                list.add(ConstantImpl.create(Convert.convert(type, val)));
                break;
            case in:
            case ni:
                ArrayList<T> inList = new ArrayList<>();
                for (String s : val.split(StrUtil.COMMA)) {
                    if (StrUtil.isBlank(s)) {
                        continue;
                    }
                    inList.add(Convert.convert(type, s));
                }
                list.add(ConstantImpl.create(inList));
                break;
            case bt:
            case nb:
                for (String s : val.split(StrUtil.COMMA)) {
                    list.add(ConstantImpl.create(Convert.convert(type, s)));
                }
                break;
            case page:
            case size:
            case asc:
            case desc:
                break;
            default:
                list.add(ConstantImpl.create(Convert.convert(type, val)));
                break;
        }
        return list;
    }

    /**
     * 根据参数值 解析 Operator
     *
     * @param value 例如： "eq:张三"
     * @return Operator
     */
    private static Operator getOps(Object value) {
        //不包含任何操作符 默认 eq
        if (!value.toString().contains(StrUtil.COLON)) {
            return Ops.EQ;
        }
        switch (getQops(value)) {
            case ne:
                return Ops.NE;
            case gt:
                return Ops.GT;
            case ge:
                return Ops.GOE;
            case lt:
                return Ops.LT;
            case le:
                return Ops.LOE;
            case lk:
            case nl:
                return Ops.LIKE;
            case nu:
                return Ops.IS_NULL;
            case nn:
                return Ops.IS_NOT_NULL;
            case in:
                return Ops.IN;
            case ni:
                return Ops.NOT_IN;
            case bt:
            case nb:
                return Ops.BETWEEN;
            case eq:
            default:
                return Ops.EQ;
        }

    }

    /**
     * 根据参数值 解析 自定义的 操作符
     *
     * @param value 例如： "eq:张三"
     * @return eq
     */
    private static ops getQops(Object value) {
        if (value.toString().length() > OPS_INDEX && value.toString().charAt(OPS_INDEX) == StrUtil.C_COLON) {
            String opt = StrUtil.subPre(value.toString(), OPS_INDEX);
            return ops.of(opt);
        }
        return ops.eq;
    }


    /***
     * sql 条件 操作符
     */
    public enum ops {
        /**
         * 等于 =
         */
        eq,
        /**
         * 不等于 !=
         */
        ne,
        /**
         * 大于 >
         */
        gt,
        /**
         * 大于等于 >=
         */
        ge,
        /**
         * 小于 <
         */
        lt,
        /**
         * 小于等于 <=
         */
        le,
        /**
         * 模糊搜索 默认：like %%
         */
        lk,
        /**
         * 模糊搜索取反 默认： not like %%
         */
        nl,
        /**
         * 为空  is null
         */
        nu,
        /**
         * 不为空  is not null
         */
        nn,
        /**
         * 包含给定列表选项  in
         */
        in,
        /**
         * 不包含给定列表选项 not in
         */
        ni,
        /**
         * 介于两者之间(包括a，不包括b)  between a,b
         */
        bt,
        /**
         * 不 介于两者之间 (包括a，不包括b)  not between a,b
         */
        nb,
        /**
         * 页码
         */
        page,
        /**
         * 每页条数
         */
        size,
        /***
         * 正序
         */
        asc,
        /**
         * 倒序
         */
        desc;

        /**
         * 根据name 解析 默认 eq
         *
         * @param name name
         * @return ops
         */
        public static ops of(String name) {
            for (ops ops : values()) {
                if (name.toLowerCase().equals(ops.name())) {
                    return ops;
                }
            }
            return eq;
        }

        /**
         * 拼接query语法 eq:val
         *
         * @param val val
         * @return eq:val
         */
        public static String eq(String val) {
            return StrUtil.format("{}:{}", eq.name(), val);
        }

        /**
         * 拼接query语法 ne:val
         *
         * @param val val
         * @return ne:val
         */
        public static String ne(String val) {
            return StrUtil.format("{}:{}", ne.name(), val);
        }

        /**
         * 拼接query语法 gt:val
         *
         * @param val val
         * @return gt:val
         */
        public static String gt(String val) {
            return StrUtil.format("{}:{}", gt.name(), val);
        }

        /**
         * 拼接query语法 ge:val
         *
         * @param val val
         * @return ge:val
         */
        public static String ge(String val) {
            return StrUtil.format("{}:{}", ge.name(), val);
        }

        /**
         * 拼接query语法 lt:val
         *
         * @param val val
         * @return lt:val
         */
        public static String lt(String val) {
            return StrUtil.format("{}:{}", lt.name(), val);
        }

        /**
         * 拼接query语法 le:val
         *
         * @param val val
         * @return le:val
         */
        public static String le(String val) {
            return StrUtil.format("{}:{}", le.name(), val);
        }

        /**
         * 拼接query语法 lk:val
         *
         * @param val val
         * @return lk:val
         */
        public static String lk(String val) {
            return StrUtil.format("{}:{}", lk.name(), val);
        }

        /**
         * 拼接query语法 nl:val
         *
         * @param val val
         * @return nl:val
         */
        public static String nl(String val) {
            return StrUtil.format("{}:{}", nl.name(), val);
        }

        /**
         * 拼接query语法 nu:val
         *
         * @param val val
         * @return nu:val
         */
        public static String nu(String val) {
            return StrUtil.format("{}:{}", nu.name(), val);
        }

        /**
         * 拼接query语法 nn:val
         *
         * @param val val
         * @return nn:val
         */
        public static String nn(String val) {
            return StrUtil.format("{}:{}", nn.name(), val);
        }

        /**
         * 拼接query语法 in:val
         *
         * @param val val
         * @return in:val
         */
        public static String in(String val) {
            return StrUtil.format("{}:{}", in.name(), val);
        }

        /**
         * 拼接query语法 in:val
         *
         * @param vals vals
         * @return in:val
         */
        public static String in(Collection<?> vals) {
            return StrUtil.format("{}:{}", in.name(), CollUtil.join(vals, ","));
        }

        /**
         * 拼接query语法 ni:val
         *
         * @param val val
         * @return ni:val
         */
        public static String ni(String val) {
            return StrUtil.format("{}:{}", ni.name(), val);
        }

        /**
         * 拼接query语法 ni:val
         *
         * @param vals val
         * @return ni:val
         */
        public static String ni(Collection<?> vals) {
            return StrUtil.format("{}:{}", ni.name(), CollUtil.join(vals, ","));
        }

        /**
         * 拼接query语法 bt:v1,v2
         *
         * @param v1 v1
         * @param v2 v2
         * @return bt:v1,v2
         */
        public static String bt(String v1, String v2) {
            return StrUtil.format("{}:{},{}", bt.name(), v1, v2);
        }

        /**
         * 拼接query语法 nb:v1,v2
         *
         * @param v1 v1
         * @param v2 v2
         * @return nb:v1,v2
         */
        public static String nb(String v1, String v2) {
            return StrUtil.format("{}:{},{}", nb.name(), v1, v2);
        }

    }

}
